home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue27 / collate / COLLATE.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1997-09-02  |  15.5 KB  |  532 lines

  1. (********************************************************************)
  2. (* Collate.PAS                                                      *)
  3. (* Collation table class                                            *)
  4. (*                                                                  *)
  5. (* (c) Julian M Bucknall, 1997                                      *)
  6. (********************************************************************)
  7.  
  8. { Notes:
  9.   There are two classes in this unit: TSortString and TCollation. The
  10.   former class is an internal class used to store the sort values for
  11.   a string, which was converted with a TCollation class instance. The
  12.   TCollation class is the interesting one: it defines a complete
  13.   collation table.
  14.  
  15.   Some documentation for TCollation:
  16.     constructor Create;
  17.     - creates an instance of a collation table. The table, by default,
  18.       is a simple binary collation (ie, it works in a similar manner
  19.       to the Delphi < string operator).
  20.  
  21.     destructor Destroy; override;
  22.     - destroys an instance of a collation table
  23.  
  24.     procedure LoadFromStream(Stream : TStream);
  25.     - loads the collation table data froma stream.
  26.  
  27.     procedure LoadFromFile(FileName : string);
  28.     - loads the collation table data from a collation file. These \
  29.       files have extension CLL.
  30.  
  31.     function CompareStrings(const S1, S2 : string) : integer;
  32.     - compares two strings according to the collation table. The
  33.       result is <0 if S1 < S2, 0 if they compare equal, >0 otherwise.
  34.  
  35.     function CompareSortStrings(const SS1, SS2 : TSortString) : integer;
  36.     - compares two sort strings previously converted from strings
  37.       using ConvertText. The result is <0 if S1 < S2, 0 if they
  38.       compare equal, >0 otherwise.
  39.  
  40.     function ConvertText(const TextStr : string) : TSortString;
  41.     - converts a string into its sort string equivalent. The method
  42.       creates a new instance of a TSortString, and you are responsible
  43.       for destroying it by calling its destructor via Destroy or Free
  44.       once you no longer need it.
  45.  
  46.     property Description : string
  47.     - a read-only property that is the description of the collation
  48.       table. This is retrieved from the collation file.
  49.  
  50.     property IsBinary : boolean
  51.     - a read-only property that is true if the collation table is a
  52.       simple binary collation, or false if it is not.
  53.  
  54.   Limitations:
  55.     - at present the only way to provide data for a collation table is
  56.       via the LoadFromFile method.
  57.  
  58.   Collation files:
  59.   The collation file layout is a stream-based one. Conceptually it
  60.   starts with the following format:
  61.  
  62.     <description string>
  63.     ^Z character
  64.     <boolean for whether the collation is binary or not>
  65.  
  66.   If the collation table is binary the file ends there. For a non-
  67.   binary collation, it continues:
  68.  
  69.     <count of ligatures as a byte>
  70.     <array of TLigature records (see below)>
  71.     <count of double characters as a byte>
  72.     <array of TDoubleChar records (see below)>
  73.     <256 bytes for TSortValues instance>
  74.  
  75.   Obviously if one of the two counts is zero, the corresponding array
  76.   does not exist; the the number of elements in the array in the
  77.   stream equals the corresponding count.
  78. }
  79.  
  80. unit Collate;
  81.  
  82. interface
  83.  
  84. uses
  85.   Classes;
  86.  
  87. type
  88.   PSortData = ^TSortData;
  89.   TSortData = packed record {Internal representation of a sort string}
  90.     sdSize : longint;
  91.     sdLen  : longint;
  92.     sdVals : array [0..1023] of byte;
  93.   end;
  94.  
  95.   TSortString = class       {A sort string class}
  96.     protected {private}
  97.       ssData : PSortData;
  98.     protected
  99.       function GetLength : longint;
  100.       function GetSize : longint;
  101.       function GetValuePtr : pointer;
  102.       procedure SetLength(L : longint);
  103.     public
  104.       constructor Create(aSize : longint);
  105.       destructor Destroy; override;
  106.       function Compare(aSS : TSortString) : integer;
  107.         {Compare with another sort string; return <0 if self is less
  108.          than aSS, 0 if equal, >0 otherwise}
  109.       procedure Grow(aSize : longint);
  110.         {Grow the sort string to accomodate aSize values}
  111.       procedure Minimize;
  112.         {Minimize the sort string so that its size matches its length}
  113.       property Size : longint
  114.          read GetSize;
  115.         {Size of the sort string, ie max num of values}
  116.       property Length : longint
  117.          read GetLength write SetLength;
  118.         {Length of the sort string, < Size}
  119.       property ValuePtr : pointer
  120.          read GetValuePtr;
  121.         {Pointer to array of sort values}
  122.   end;
  123.  
  124. type
  125.   TLigature = packed record
  126.     lLig   : AnsiChar;    {ligature character}
  127.     lSVal1 : byte;        {sort value of first character}
  128.     lSVal2 : byte;        {sort value of second character}
  129.   end;
  130.   PLigatureArray = ^TLigatureArray;
  131.   TLigatureArray = array [0..255] of TLigature;
  132.  
  133.   TDoubleChar = packed record
  134.     dcChar1 : AnsiChar;   {first character}
  135.     dcChar2 : AnsiChar;   {second character}
  136.     dcSortValue : byte;   {sort value of double character}
  137.   end;
  138.   PDoubleCharArray = ^TDoubleCharArray;
  139.   TDoubleCharArray = array [0..255] of TDoubleChar;
  140.  
  141.   TSortValues = array [0..255] of byte;
  142.  
  143.   TCollation = class
  144.     protected {private}
  145.       FBinary   : boolean;
  146.       FDesc     : string;
  147.       FLigCount  : integer;
  148.       FLigArray : PLigatureArray;
  149.       FDCACount : integer;
  150.       FDblArray : PDoubleCharArray;
  151.       FSortVals : TSortValues;
  152.  
  153.       SS1       : TSortString;
  154.       SS2       : TSortString;
  155.     protected
  156.       procedure clConvertText(const S  : string;
  157.                               const SS : TSortString);
  158.     public
  159.       constructor Create;
  160.       destructor Destroy; override;
  161.  
  162.       procedure LoadFromStream(Stream : TStream);
  163.       procedure LoadFromFile(FileName : string);
  164.  
  165.       function CompareStrings(const S1, S2 : string) : integer;
  166.       function CompareSortStrings(const SS1, SS2 : TSortString) : integer;
  167.       function ConvertText(const TextStr : string) : TSortString;
  168.  
  169.       property Description : string
  170.          read FDesc;
  171.       property IsBinary : boolean
  172.          read FBinary;
  173.   end;
  174.  
  175. implementation
  176.  
  177. uses
  178.   SysUtils;
  179.  
  180. {===Sort String Routines=============================================}
  181. constructor TSortString.Create(aSize : longint);
  182. begin
  183.   {inherited Create;}
  184.   GetMem(ssData, (2 * sizeof(longint)) + aSize);
  185.   ssData^.sdSize := aSize;
  186.   ssData^.sdLen := 0;
  187. end;
  188. {--------}
  189. destructor TSortString.Destroy;
  190. begin
  191.   if (ssData <> nil) then
  192.     FreeMem(ssData, (2 * sizeof(longint)) + ssData^.sdSize);
  193.   {inherited Destroy;}
  194. end;
  195. {--------}
  196. function TSortString.Compare(aSS : TSortString) : integer;
  197. asm
  198.   {EAX = Self}
  199.   {EDX = aSS}
  200.   push ebx
  201.   push esi
  202.   push edi
  203.   push ebp
  204.  
  205.   mov esi, [eax].TSortString.ssData
  206.   mov ebp, [esi+4]                  {ebp = length self sort data}
  207.   add esi, 8                        {esi => self sort data}
  208.   mov edi, [edx].TSortString.ssData
  209.   mov edx, [edi+4]                  {edx = length aSS sort data}
  210.   add edi, 8                        {edi => aSS sort data}
  211.  
  212.   xor eax, eax                      {assume equal}
  213.  
  214.   mov ecx, ebp                      {calculate the smaller length}
  215.   cmp ecx, edx
  216.   jb @@GotMinLength
  217.   mov ecx, edx
  218.  
  219. @@GotMinLength:
  220.   push ecx                          {save shorter length}
  221.   shr ecx, 2                        {divide by 4}
  222.   jz @@DoRemainder
  223. @@EightByteLoop:                    {compare bytes 8 at a time}
  224.   mov ebx, [esi]
  225.   cmp ebx, [edi]
  226.   jne @@RecompareBytes
  227.   dec ecx
  228.   jz @@DoRemainderPlus4
  229.   mov ebx, [esi+4]
  230.   cmp ebx, [edi+4]
  231.   jne @@RecompareBytesPlus4
  232.   add esi, 8
  233.   add edi, 8
  234.   dec ecx
  235.   jnz @@EightByteLoop
  236.   jmp @@DoRemainder
  237.  
  238. @@RecompareBytesPlus4:              {mismatch - recompare bytes}
  239.   add esi, 4
  240.   add edi, 4
  241. @@RecompareBytes:
  242.   pop ecx                           {get shorter length & discard}
  243.   mov ecx, 4
  244.   jmp @@CompareBytes
  245.  
  246. @@DoRemainderPlus4:                 {do remaining bytes}
  247.   add esi, 4
  248.   add edi, 4
  249. @@DoRemainder:
  250.   pop ecx                           {get shorter length}
  251.   and ecx, 3                        {..mod 4}
  252.   jz @@EQ
  253.  
  254. @@CompareBytes:                     {compare up to 4 bytes}
  255.   mov bl, [esi]                     {first byte}
  256.   cmp bl, [edi]
  257.   jb @@LT
  258.   ja @@GT
  259.   dec ecx
  260.   jz @@EQ
  261.   mov bl, [esi+1]                   {second byte}
  262.   cmp bl, [edi+1]
  263.   jb @@LT
  264.   ja @@GT
  265.   dec ecx
  266.   jz @@EQ
  267.   mov bl, [esi+2]                   {third byte}
  268.   cmp bl, [edi+2]
  269.   jb @@LT
  270.   ja @@GT
  271.   dec ecx
  272.   jz @@EQ
  273.   mov bl, [esi+3]                   {fourth byte, only on mismatch}
  274.   cmp bl, [edi+3]
  275.   jb @@LT
  276.   ja @@GT
  277.  
  278. @@EQ:                               {bytes are equal}
  279.   cmp ebp, edx                      {compare old lengths}
  280.   jb @@LT
  281.   ja @@GT
  282.   inc eax
  283.  
  284. @@LT:
  285.   dec eax
  286.   dec eax
  287.  
  288. @@GT:
  289.   inc eax
  290.  
  291. @@Exit:
  292.   pop ebp
  293.   pop edi
  294.   pop esi
  295.   pop ebx
  296. end;
  297. {--------}
  298. function TSortString.GetLength : longint;
  299. begin
  300.   Result := ssData^.sdLen;
  301. end;
  302. {--------}
  303. function TSortString.GetSize : longint;
  304. begin
  305.   Result := ssData^.sdSize;
  306. end;
  307. {--------}
  308. function TSortString.GetValuePtr : pointer;
  309. begin
  310.   Result := @ssData^.sdVals;
  311. end;
  312. {--------}
  313. procedure TSortString.Grow(aSize : longint);
  314. begin
  315.   if (ssData <> nil) and (aSize > ssData^.sdSize) then begin
  316.     ReallocMem(ssData, (2 * sizeof(longint)) + aSize);
  317.     ssData^.sdSize := aSize;
  318.   end;
  319. end;
  320. {--------}
  321. procedure TSortString.Minimize;
  322. begin
  323.   if (ssData <> nil) and (ssData^.sdLen < ssData^.sdSize) then begin
  324.     ReallocMem(ssData, (2 * sizeof(longint)) + ssData^.sdLen);
  325.     ssData^.sdSize := ssData^.sdLen;
  326.   end;
  327. end;
  328. {--------}
  329. procedure TSortString.SetLength(L : longint);
  330. begin
  331.   ssData^.sdLen := L;
  332. end;
  333. {====================================================================}
  334.  
  335. {===TCollation=======================================================}
  336. constructor TCollation.Create;
  337. begin
  338.   inherited Create;
  339.   {preallocate two buffers for converted strings, 512 bytes should be
  340.    ample for the vast majority of cases}
  341.   SS1 := TSortString.Create(512);
  342.   SS2 := TSortString.Create(512);
  343.   {initialize the sortvalues array}
  344.   FillChar(FSortVals, sizeof(FSortVals), 0);
  345.   {the collation is binary to begin with}
  346.   FBinary := true;
  347. end;
  348. {--------}
  349. destructor TCollation.Destroy;
  350. begin
  351.   {destroy our buffers}
  352.   SS1.Free;
  353.   SS2.Free;
  354.   {destroy our ligature and doublechar arrays}
  355.   if (FLigCount <> 0) and (FLigArray <> nil) then
  356.     FreeMem(FLigArray, FLigCount * sizeof(TLigature));
  357.   if (FDCACount <> 0) and (FDblArray <> nil) then
  358.     FreeMem(FDblArray, FDCACount * sizeof(TDoubleChar));
  359.   {continue the destroy}
  360.   inherited Destroy;
  361. end;
  362. {--------}
  363. procedure TCollation.clConvertText(const S  : string;
  364.                                    const SS : TSortString);
  365. var
  366.   ChInx     : integer;
  367.   i         : integer;
  368.   ConvCount : integer;
  369.   ConvVals  : PByteArray;
  370.   Ch        : AnsiChar;
  371.   UsedLigature : boolean;
  372.   UsedDouble   : boolean;
  373.   CheckDoubles : boolean;
  374. begin
  375.   {set up some variables ready for the loop}
  376.   ConvCount := 0;
  377.   ConvVals := PByteArray(SS.ValuePtr);
  378.   UsedLigature := false;
  379.   UsedDouble := false;
  380.   CheckDoubles := FDCACount <> 0;
  381.   {convert each character into its sort value equivalent}
  382.   for ChInx := 1 to length(S) do begin
  383.     {save time: get the character into a local variable}
  384.     Ch := S[ChInx];
  385.     {check our ligatures}
  386.     for i := 0 to pred(FLigCount) do begin
  387.       if (Ch = FLigArray[i].lLig) then begin
  388.         UsedLigature := true;
  389.         ConvVals[ConvCount] := FLigArray[i].lSVal1;
  390.         ConvVals[ConvCount+1] := FLigArray[i].lSVal2;
  391.         inc(ConvCount, 2);
  392.         Break;
  393.       end;
  394.     end;
  395.     if UsedLigature then begin
  396.       UsedLigature := false;
  397.     end
  398.     else {a ligature was not found} begin
  399.       {check our double characters, if required}
  400.       if CheckDoubles then begin
  401.         if (ChInx < length(S)) then begin
  402.           for i := 0 to pred(FDCACount) do begin
  403.             if (Ch = FDblArray[i].dcChar1) and
  404.                (S[ChInx+1] = FDblArray[i].dcChar2) then begin
  405.               UsedDouble := true;
  406.               ConvVals[ConvCount] := FDblArray[i].dcSortValue;
  407.               inc(ConvCount);
  408.               Break;
  409.             end;
  410.           end;
  411.         end;
  412.         if UsedDouble then begin
  413.           UsedDouble := false;
  414.         end
  415.         else {a double char was not found} begin
  416.           ConvVals[ConvCount] := FSortVals[ord(Ch)];
  417.           inc(ConvCount);
  418.         end;
  419.       end
  420.       else {no double chars to check for} begin
  421.         ConvVals[ConvCount] := FSortVals[ord(Ch)];
  422.         inc(ConvCount);
  423.       end;
  424.     end;
  425.   end;
  426.   {set the length of the sort string}
  427.   SS.Length := ConvCount;
  428. end;
  429. {--------}
  430. function TCollation.CompareSortStrings(const SS1, SS2 : TSortString) : integer;
  431. begin
  432.   Result := SS1.Compare(SS2);
  433. end;
  434. {--------}
  435. function TCollation.CompareStrings(const S1, S2 : string) : integer;
  436. begin
  437.   if IsBinary then
  438.     {if it's a binary collation, the comparison is a simple binary
  439.      comparison}
  440.     Result := SysUtils.CompareStr(S1, S2)
  441.   else begin
  442.     {otherwise there's some conversion to do; convert the strings}
  443.     if (SS1.Size < (length(S1) * 2)) then
  444.       SS1.Grow(length(S1) * 2);
  445.     clConvertText(S1, SS1);
  446.     if (SS2.Size < (length(S2) * 2)) then
  447.       SS2.Grow(length(S1) * 2);
  448.     clConvertText(S2, SS2);
  449.     {compare the two sort strings}
  450.     Result := SS1.Compare(SS2);
  451.   end;
  452. end;
  453. {--------}
  454. function TCollation.ConvertText(const TextStr : string) : TSortString;
  455. var
  456.   TextLen : integer;
  457. begin
  458.   if IsBinary then begin
  459.     {if it's a binary collation, the converted string is the same as
  460.      the original}
  461.     TextLen := length(TextStr);
  462.     Result := TSortString.Create(TextLen);
  463.     Move(TextStr[1], Result.ValuePtr^, TextLen);
  464.     Result.Length := TextLen;
  465.   end
  466.   else begin
  467.     {otherwise there's some conversion to do; preallocate the
  468.      converted string result - we assume every character is a
  469.      ligature}
  470.     Result := TSortString.Create(length(TextStr) * 2);
  471.     {convert the string}
  472.     clConvertText(TextStr, Result);
  473.     {tidy up by setting the correct length of the result}
  474.     Result.Minimize;
  475.   end;
  476. end;
  477. {--------}
  478. procedure TCollation.LoadFromFile(FileName : string);
  479. var
  480.   S : TStream;
  481. begin
  482.   S := TFileStream.Create(FileName, fmOpenRead + fmShareDenyWrite);
  483.   try
  484.     LoadFromStream(S);
  485.   finally
  486.     S.Free;
  487.   end;{try..finally}
  488. end;
  489. {--------}
  490. procedure TCollation.LoadFromStream(Stream : TStream);
  491. var
  492.   i         : integer;
  493.   ActualLen : integer;
  494.   NumBytes  : integer;
  495.   StrmPos   : longint;
  496. begin
  497.   with Stream do begin
  498.     SetLength(FDesc, 256);
  499.     StrmPos := Position;
  500.     Read(FDesc[1], 256);
  501.     ActualLen := 0;
  502.     for i := 1 to 256 do
  503.       if (FDesc[i] = ^Z) then begin
  504.         ActualLen := i-1;
  505.         Break;
  506.       end;
  507.     SetLength(FDesc, ActualLen);
  508.     Position := StrmPos + ActualLen + 1;
  509.     Read(FBinary, sizeof(FBinary));
  510.     if not IsBinary then begin
  511.       FLigCount := 0;
  512.       Read(FLigCount, sizeof(byte));
  513.       if (FLigCount <> 0) then begin
  514.         NumBytes := FLigCount * sizeof(TLigature);
  515.         GetMem(FLigArray, NumBytes);
  516.         Read(FLigArray^, NumBytes);
  517.       end;
  518.       FDCACount := 0;
  519.       Read(FDCACount, sizeof(byte));
  520.       if (FDCACount <> 0) then begin
  521.         NumBytes := FDCACount * sizeof(TDoubleChar);
  522.         GetMem(FDblArray, NumBytes);
  523.         Read(FDblArray^, NumBytes);
  524.       end;
  525.       Read(FSortVals, sizeof(FSortVals));
  526.     end;
  527.   end;
  528. end;
  529. {====================================================================}
  530.  
  531. end.
  532.